Skip to content

Resolve the pilot package drift the gate, the lint rules and decision 0004 each found - #1175

Closed
schickling-assistant wants to merge 2 commits into
schickling-assistant/2026-09-01-lint-toolchainfrom
schickling-assistant/2026-09-01-pilot-drift
Closed

schickling-assistant wants to merge 2 commits into
schickling-assistant/2026-09-01-lint-toolchainfrom
schickling-assistant/2026-09-01-pilot-drift

Conversation

@schickling-assistant

@schickling-assistant schickling-assistant commented Sep 1, 2026 •

Copy link
Copy Markdown
Collaborator

Resolves the pilot package's three independent drift findings. One PR rather
than three because all three were deferred on the same prerequisite — a working
visual gate for this package — which #1170 now supplies. Closes #1171.

What was wrong, and how each part was proved

class fix gate evidence
4 raw colour literals on-primary and shadow-raised semantic tokens, defaults from named scale steps 0 pixels moved
13 legacy top-level pseudo-class sites nested condition objects; the 2 competing ones moved to ordered stylex.props arguments 0 pixels moved
lifted / re-derived React state React Aria render prop for ancestor-owned state; additive override instead of mutually exclusive objects 0 pixels moved
10 colour-contrast violations primary blue500 -> blue600 18 stories, recolour only
1 duplicate-banner violation Header -> plain element + aria-label on the group 0 pixels moved

All 17 oxlint-disable suppressions are gone; all eight rules gate the package
again.

Gate adjudication

Baseline 98037687a (#1172 tip): 39 compared, 28/39 passed at baseline, 20
changed.
Non-degenerate, so the pre-existing subtraction is not masking
anything.

  • 18 changed are intended. Every differing pixel is 43,127,255 ->
    21,93,252, confined to selected segments, checkbox boxes and the accent
    tick. blue500 measured 3.76:1 against white body text where AA requires
    4.5:1; blue600 measures 5.25:1. Renaming the literal to on-primary alone
    would have preserved the violation behind a better name.
  • 2 changed are the gate's own sub-pixel fringe, not this change. Proved,
    not assumed: recapturing the unchanged baseline tree reproduces both diffs
    identically — URL n=689, With Hint n=693, same bounding boxes, max channel
    delta 2/255 on one border hairline. Comparing that recapture against this
    branch yields exactly 18 and no fringe. Reported to GateFixes, including
    that it is a deterministic first-capture effect rather than random noise.
  • 0 accessibility failures remain.

The zero-pixel rows are the substantive result. The recorded hazard was that
nesting a pseudo-class changes which condition wins; the gate shows the
refactor was behaviour-preserving.

Two things deliberately NOT done

  • The focus ring stays boxShadow rather than being converted to outline.
    The partitioning invariant already holds — nothing else on these elements sets
    outline*, and stylex-outline-focus-visible-only confirms it — and no story
    renders a focused element, so repainting the ring would be a visual change the
    gate structurally cannot adjudicate. Named follow-up: add a story that
    renders a focused element, then convert with the gate watching. That turns an
    unverifiable change into a verified one and leaves a permanent regression test
    for a hazard that is otherwise invisible to both the type checker and the
    linter.
  • Two changes are outside the gate's reach and are named as such: the
    shadow-raised token (no story opens the dropdown) and the focus-visible move
    (no story focuses anything).

Verification

  • Gate: as above.
  • lint:check:oxlint: clean.
  • Pilot unit snapshots regenerated (<header> gone, aria-label present).

Note the reproducible close timed out after 10000ms / something prevents the main process from exiting on every gate run: known upstream StyleX bundler
plugin defect (leaked handles), not ours, recorded so nobody investigates it
from scratch.

Posted on behalf of @schickling
field value
agent_identity dev3.direct.omp.6xjdfab7
session dev3.6xjdfab7
agent_persona generalist
agent_supervisor unavailable
agent_tool OMP
agent_tool_version 18.0.11
agent_runtime OMP 18.0.11
tooling_profile dotfiles@5a8d579-dirty

… findings

Three separate checks each found real drift in this package, none of them
looking for it: the state-idiom review behind decision 0004, the StyleX lint
rules the package's own patterns produced, and the first run of the visual and
accessibility gate. All three were deferred on the same prerequisite — a working
visual gate for this package — which the gate now supplies, so they are settled
together rather than three times over.

Raw colours become semantic tokens. `on-primary` is the foreground for anything
drawn on a `primary` background; `shadow-raised` is the popover elevation and
takes its default from the `shadows.lg` scale step rather than restating the
rgba stack. Both defaults reference a named scale value, so the palette choice
stays in one reviewable file.

`primary` moves from `blue500` to `blue600`, and that is a legibility fact
rather than a preference. The accessibility gate failed ten of this package's
thirty-nine stories on exactly one thing: white body text on the selected
segment measured 3.76:1 where AA requires 4.5:1. `blue600` measures 5.25:1.
Renaming the literal to `on-primary` would have preserved the violation behind a
nicer name. `accent` follows `primary` so the brand blue stays one colour.

The thirteen deprecated top-level pseudo-class sites move into condition
objects. This is the change the deferral was actually about, because nesting a
pseudo-class changes which condition wins, so every site was checked for a
competing state on the same property rather than translated mechanically. Eleven
of the thirteen set a property nothing else touches and carry no precedence risk
at all. The two that do compete — the segmented control's and the list option's
background under hover versus selection — are resolved by application order
instead: hover lives in the base style, selection is a later `stylex.props`
argument, and it restates the hover value because a later unconditional
`backgroundColor` does not replace an earlier `backgroundColor` under a `:hover`
key. That is the R06 shape, and it means the outcome no longer depends on
attribute conditions outranking pseudo-class ones.

Focus moves from `:focus` to the accessible-component library's own
focus-visible state where the element is one of its components, and to the
native `:focus-visible` on the one plain `<input>` that is not. A pointer click
no longer paints a keyboard focus ring. Note what this is NOT: the ring is still
drawn with `boxShadow` rather than converted to `outline`. The partitioning
invariant already holds here — nothing else on these elements sets `outline*`,
and the lint rule confirms it — and no story exercises a focused element, so
repainting the ring would be a visual change the gate structurally cannot
adjudicate. Recorded as follow-up rather than done blind.

State resolution stops re-deriving what the component already knows. The
checkbox derived its box style from its own `value` prop; selection lives on the
CheckboxButton, which is an ancestor of the box, so the box cannot read it as
one of its own conditions and React Aria's render prop is the sanctioned
mechanism for that case. The segmented control and the list options branched
between two mutually exclusive style objects, hover rule included; they now
apply one additive override in argument order.

The accessibility gate's eleventh failure was structural, not colour. React
Aria's `Header` renders `<header>`, and a `<header>` outside sectioning content
is a `banner` landmark, so two nested field groups produced two banners and
tripped `landmark-no-duplicate-banner` and `landmark-unique`. The label is now a
plain element and the group carries the accessible name it was missing.

The seventeen per-line `oxlint-disable` suppressions are gone; the package is
gated by all eight rules again.
@schickling-assistant schickling-assistant changed the title schickling assistant/2026 09 01 pilot drift Resolve the pilot package drift the gate, the lint rules and decision 0004 each found Sep 1, 2026
@schickling-assistant

Copy link
Copy Markdown
Collaborator Author

Superseded by #1191, which collapses this stack onto main as one tree.

Closing rather than merging: propagating main through eight stacked branches meant re-reconciling the lockfile, the fixed-output hashes and the Buck projection once per branch. Branch 2 alone produced 11 conflicts, a semantically broken auto-merged lockfile, a BUCK admission rename and a circular tooling block. On one tree each of those happens once.

This PR's content is in #1191, verified with git merge-base --is-ancestor over all eight heads — #1184 was found missing that way and merged in, because a gh stack base does not make a branch contain that base.

This body stays as the record of the per-change evidence, which #1191 summarises but does not reproduce in full.

Posted on behalf of @schickling
field value
agent_identity dev3.direct.omp.mhx73ur2
session dev3.mhx73ur2
agent_persona generalist
agent_supervisor unavailable
agent_tool OMP
agent_tool_version 18.0.11
agent_runtime OMP 18.0.11
tooling_profile dotfiles@e8f0615-dirty

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate effect-schema-form-aria onto the StyleX enforcement rules

1 participant